## -*- test-case-name: activist.command -*-
## all commands available; these are run through the CommandProcessor
## which serialises them and then puts them in a queue to be processed.
##
## TODO: interface-ify this shit
##


from activist.Data import Data
from activist.DataStore import DataStore
from activist.MetaData import relationshipFromName, MetaData

class Command:
    def doCommand(self):
        """this should be overridden to actally do whatever the command does..."""
        pass

class IdProxy:
    """represents something in the DataStore so that
    we can pickle it and get back the "real" object.

    NOTE: presumes that the "real" object will already exist in the
    DataStore; this is safe for command objects...
    """
    def __init__(self,obj):
        self._id = obj.id()
        self._type = obj.__class__

    def unproxy(self):
        rtn = DataStore[self._id]
        if rtn.__class__ != self._type:
            raise "wrong type of object for ID %s (expected: %s vs. got: %s)" % (self._id,str(self._type),str(rtn.__class__))
        return rtn

class MetaProxy:
    """represents one of the inner-classes of MetaData
    to make pickling possible (might be better fix FIXME TODO)"""

    def __init__(self,meta):
        self._meta = str(meta)

    def unproxy(self):
        return relationshipFromName(self._meta)
    

class GenericCommand:
    def __init__(self,fn,args):
        """pass a callable function "fn" which takes
        the tuple "args" as arguments...

        (note that the function must be picklable...)"""

        self.args = args
        self.fn = fn

    def doCommand(self):
        return apply(self.fn,self.args)

    def __str__(self):
        return '<GenericCommand: %s, %d args>' % (str(self.fn),len(self.args))

    def __getstate__(self):
        newargs = []
        rtn = {}
        for key in self.__dict__.keys():
            if key != 'args':
                rtn[key] = self.__dict__[key]
                
        for arg in self.__dict__['args']:
            newarg = None
            print "ARG",arg,repr(arg)
            if isinstance(arg,Data):
                newarg = IdProxy(arg)

            try:
                if isinstance(arg,MetaData.Relationship) or issubclass(arg,MetaData.Relationship):
                    print "found META"
                    newarg = MetaProxy(arg)
            except TypeError:
                pass
                
            if newarg:
                print "PROXY",newarg,"for",arg
                newargs.append(newarg)
            else:
                print "NOPROXY",arg
                newargs.append(arg)
        rtn['args'] = tuple(newargs)
        return rtn

    def __setstate__(self,dict):
        #print "COMMAND:setstate",dict
        self.__dict__ = dict
        args = self.__dict__['args']
        newargs = []
        print "args:",
        for arg in args:
            s = str(arg)
            if len(s) > 30:
                print s[:30]+'..., ',
            else:
                print s,', ',
        print

        for value in self.__dict__['args']:
            newvalue = None
            if isinstance(value,IdProxy):
                newvalue = value.unproxy()
                print "  unproxy'd:",newvalue

            elif isinstance(value,MetaProxy):
                newvalue = value.unproxy()
                print "  unproxy'd:",newvalue
                
            if newvalue:
                newargs.append(newvalue)
                print "  swapped",value,"for",newvalue
            else:
                newargs.append(value)
                
        self.__dict__['args'] = tuple(newargs)


class Checkpoint(Command):
    """
    save the database and reset the command-history
    """

    def doCommand(self):
        from activist.CommandProcessor import CommandProcessor
        return CommandProcessor.checkpoint()

class NukeObject(Command):
    """
    remove some object and its associated metadata
    """

    def __init__(self,obj):
        self.obj = IdProxy(obj)

    def doCommand(self):
        from activist.DataStore import DataStore

        obj = self.obj.unproxy()
        
        DataStore.remove(obj)
        targets = []
        for meta in obj.metadata():
            print "  removing:",meta
            if meta.fromData() == obj:
                targets.append(meta.toData())
            else:
                targets.append(meta.fromData())
            DataStore.remove(meta)

        for target in targets:
            if len(target.metadata()) == 0:
                print "  also removing [no metadata]:",target
            else:
                print "  target",target,"survives:"
                for m in target.metadata():
                    print "    ",m

##
## testing
##

from twisted.trial import unittest
class test_commands(unittest.TestCase):


    def testEdits(self):
        from activist.CommandProcessor import CommandProcessor
        import activist.Person
        import activist.Event
        import activist.Place

                                        # test editing Person
        
        person = activist.Person.Person('foo foo', '123-4567', 'foo@bar.com', None)
        info = person.info()
        cmd = GenericCommand(activist.Person.commandChange,
                             ( None,
                               person,
                               'mike warren',
                               ['(403) 123-4567'],
                               ['foo@bar.com','mike@somewhere.com'],
                               'an address' ) )
        import time                     # so that the metadata creation-time has a chance to be different
        time.sleep(0.1)
        CommandProcessor.process( cmd )
        self.failUnless( person.info()._name == 'mike warren' )
        self.failUnless( person.info()._phone[0] == '(403) 123-4567' )
        self.failUnless( person.info()._email[0] == 'foo@bar.com' )
        self.failUnless( person.info()._email[1] == 'mike@somewhere.com' )

                                        # test editing Event

        place0 = activist.Place.Place('some place', 'somewhere', '(403) 282-1234')
        place1 = activist.Place.Place('different place', 'somewhere', '(403) 282-4321')

        event = activist.Event.Event(person,
                                     'event title', activist.Time.now(), "an event", place0 )
        cmd = GenericCommand(activist.Event.commandChange,
                             (person,
                              event,
                              'event title', activist.Time.now(), "an *exciting* event", place1))
        import time
        time.sleep(0.1)
        CommandProcessor.process(cmd)
        self.failUnless(event.info()._description=='an *exciting* event')
        self.failUnless(event.info().where()==place1)
        self.failUnless(event.metainfo().creator() == person)

    def testRemove(self):
        from activist.CommandProcessor import CommandProcessor
        from activist.Person import Person
        from activist.DataStore import DataStore

        
        person = Person('death to me', '444-3333', 'foo2@bar.com', None)
        #DataStore._dumpXML()
        CommandProcessor.process( NukeObject(person) )

    def testCreate(self):
        """test not up-to-date"""

        raise "test not up-to-date"
